﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using Test;

namespace puzzle
{
    public class Field
    {
        public static int borderFactor = 1000;
        public static int waypointFactor = 1000;

        public int Width { get; set; }
        public int Height { get; set; }
        private Tile[,] tiles;
        private HashSet<Tile> warpers;
        private Random random;
        private MainWindow mainWaindow;
        public int DrawCount { get; set; }

        public Field(int width, int height, int nbColors, MainWindow window)
        {
            Width = width;
            Height = height;
            tiles = new Tile[width, height];
            warpers = new HashSet<Tile>();
            mainWaindow = window;
            DrawCount = 0;
            int area = width * height;
            int meanPathDist = (width + height) / 2;

            random = new Random();

            for (int i = 0; i < width; i++)
            {
                for (int j = 0; j < height; j++)
                {
                    tiles[i, j] = new Tile(i, j);
                }
            }

            addWarpers(1);

            int startX = -1, startY = -1, endX = -1, endY = -1;

            do
            {
                if (random.Next(2) == 0)
                {
                    if (random.Next(2) == 0) startX = 0;
                    else startX = width - 1;
                    startY = random.Next(height);
                }
                else
                {
                    if (random.Next(2) == 0) startY = 0;
                    else startY = height - 1;
                    startX = random.Next(width);
                }

                if (random.Next(2) == 0)
                {
                    if (random.Next(2) == 0) endX = 0;
                    else endX = width - 1;
                    endY = random.Next(height);
                }
                else
                {
                    if (random.Next(2) == 0) endY = 0;
                    else endY = height - 1;
                    endX = random.Next(width);
                }
            } while (startX == endX && startY == endY);

            int nbPath = area / meanPathDist + random.Next(3);
            int k = 0;
            int currentX = startX;
            int currentY = startY;
            List<Tile> globalPath = new List<Tile>();
            while (k < nbPath)
            {
                if (hasBlockedTiles())
                {
                    int x2, y2;
                    do
                    {
                        x2 = random.Next(width);
                        y2 = random.Next(height);
                    } while (getTile(x2, y2).Type != TileType.Blocked || Math.Abs(currentX - x2) + Math.Abs(currentY - y2) < 2 || (x2 == endX && y2 == endY)); // 2 = minPathDist

                    globalPath.AddRange(addPath(currentX, currentY, x2, y2, random.Next(nbColors)));
                    currentX = x2;
                    currentY = y2;

                    k++;
                    /*if (window != null)
                    {
                        window.draw(this);
                        DrawCount++;
                    }*/
                }
                else k = nbPath;
            }
            globalPath.AddRange(addPath(currentX, currentY, endX, endY, random.Next(nbColors)));

            checkWarpers();
            /*if (window != null)
            {
                window.draw(this);
                DrawCount++;
            }*/

            int nbSwitches = area / (meanPathDist*5) + random.Next(1, 3);
            int nbBlockers = area / (meanPathDist*5) + random.Next(1, 3);
            addSwitches(globalPath, nbSwitches, nbBlockers);

            addNormalTiles(globalPath);
        }

        public void addSwitches(List<Tile> path, int nbSwitches, int nbBlockers)
        {
            List<int> tilePos = new List<int>();
            List<int> switchPos = new List<int>();
            List<int> blockerPos = new List<int>();
            
            for (int i = 0; i < path.Count; i++)
            {
                if (path[i].Type != TileType.Warper && path[i].Type != TileType.ColorSwitch)
                {
                    tilePos.Add(i);
                }
            }

            var tmp = tilePos.GroupBy(x => x);
            foreach (var gr in tmp)
            {
                if (gr.Count() > 1) tilePos.Remove(gr.Key);
            }

            for (int i = 0; i < nbSwitches; i++)
            {
                int pos = tilePos[random.Next(tilePos.Count)];
                switchPos.Add(pos);
                tilePos.Remove(pos);
            }

            for (int i = 0; i < nbBlockers; i++)
            {
                int pos = tilePos[random.Next(tilePos.Count)];
                blockerPos.Add(pos);
                tilePos.Remove(pos);
            }

            int switchCounter = 0;
            for (int i = 0; i < path.Count; i++)
            {
                if (switchPos.Contains(i))
                {
                    path[i].Type = TileType.Switch;
                    path[i].WaypointID = -1;
                    switchCounter++;
                } 
                else if (blockerPos.Contains(i))
                {
                    if (switchCounter % 2 == 0)
                    {
                        path[i].Type = TileType.State_Passable;
                    }
                    else
                    {
                        path[i].Type = TileType.State_Blocked;
                    }
                    path[i].WaypointID = -1;
                }
            }
        }

        public void addNormalTiles(List<Tile> path)
        {
            for (int i = 0; i < path.Count - 1; i++)
            {
                if (path[i].Type == TileType.Waypoint && random.Next(20) == 0)
                {
                    path[i].Type = TileType.Normal;
                    path[i].WaypointID = -1;
                }
            }
        }

        public void addWarpers(int nb)
        {
            for (int id = 0 ; id < nb ; id++){
                int x1, x2, y1, y2;
                do
                {
                    x1 = random.Next(1 , Width - 1);
                    x2 = random.Next(1, Width - 1);
                    y1 = random.Next(1, Height - 1);
                    y2 = random.Next(1, Height - 1);
                } while (Math.Abs(x1 - x2) + Math.Abs(y1 - y2) < 2);

                Tile tile1 = tiles[x1, y1];
                Tile tile2 = tiles[x2, y2];
                tile1.WarperID = id;
                tile2.WarperID = id;
                tile1.Type = TileType.Warper;
                tile2.Type = TileType.Warper;
                warpers.Add(tile1);
                warpers.Add(tile2);

                id++;
            }
        }

        public void checkWarpers()
        {
            foreach (Tile warper in warpers)
            {
                if (is4WaysBlocked(warper))
                {
                    Tile warper1 = warper;
                    Tile warper2 = getWarpEnd(warper);
                    warper1.Type = TileType.Blocked;
                    warper1.WarperID = -1;
                    warper2.Type = TileType.Blocked;
                    warper2.WarperID = -1;
                }
            }
        }

        public Tile getWarpEnd(Tile tile)
        {
            if (tile.WarperID != -1)
            {
                foreach (Tile end in warpers)
                {
                    if (end != tile && end.WarperID == tile.WarperID) return end;
                }
                return null;
            }
            else return null;
        }

        public HashSet<Tile> getAdjacentTiles(Tile tile)
        {
            HashSet<Tile> res = new HashSet<Tile>();
            Tile adj;

            if (tile.Y < Height - 1)
            {
                adj = tiles[tile.X, tile.Y + 1];
                if (adj.WarperID == -1) res.Add(adj);
                else res.Add(getWarpEnd(adj));
            }

            if (tile.X < Width - 1)
            {
                adj = tiles[tile.X + 1, tile.Y];
                if (adj.WarperID == -1) res.Add(adj);
                else res.Add(getWarpEnd(adj));
            }

            if (tile.Y > 0)
            {
                adj = tiles[tile.X, tile.Y - 1];
                if (adj.WarperID == -1) res.Add(adj);
                else res.Add(getWarpEnd(adj));
            }

            if (tile.X > 0)
            {
                adj = tiles[tile.X - 1, tile.Y];
                if (adj.WarperID == -1) res.Add(adj);
                else res.Add(getWarpEnd(adj));
            }

            res.RemoveWhere(isColorSwitch);

            return res;
        }

        public Tile getTile(int x, int y)
        {
            return tiles[x, y];
        }


        public int getTileValue(Tile tile, int colorID)
        {
            if (tile.Type == TileType.Waypoint)
            {
                return waypointFactor;
            }
            else return 1;
        }

        public bool isColorSwitch(Tile tile)
        {
            return tile.Type == TileType.ColorSwitch;
        }

        public bool isBorder(Tile tile)
        {
            return tile.X == 0 || tile.Y == 0 || tile.X == Width - 1 || tile.Y == Height - 1;
        }

        public bool is4WaysBlocked(Tile tile)
        {
            if (tiles[tile.X + 1, tile.Y].Type != TileType.Blocked) return false;
            if (tiles[tile.X, tile.Y + 1].Type != TileType.Blocked) return false;
            if (tiles[tile.X - 1, tile.Y].Type != TileType.Blocked) return false;
            if (tiles[tile.X, tile.Y - 1].Type != TileType.Blocked) return false;
            return true;
        }

        public bool hasBlockedTiles(){
            for (int i = 0; i < Width; i++)
            {
                for (int j = 0; j < Height; j++)
                {
                    if (tiles[i, j].Type == TileType.Blocked) return true;
                }
            }
            return false;
        }

        public HashSet<Tile> getBorderTiles()
        {
            HashSet<Tile> res = new HashSet<Tile>();

            for (int i = 0; i < Width; i++)
            {
                for (int j = 0; j < Height; j++)
                {
                    if (isBorder(tiles[i,j]))
                    {
                        res.Add(tiles[i, j]);
                    }
                }
            }

            res.RemoveWhere(isColorSwitch);

            return res;
        }

        public List<Tile> addPath(int x1, int y1, int x2, int y2, int colorID)
        {
            int[,] pathValue = new int[Width,Height];
            Tile[,] previousTile = new Tile[Width, Height];

            HashSet<Tile> tilePool = new HashSet<Tile>();
            HashSet<Tile> markedTiles = new HashSet<Tile>();

            List<Tile> tmp = new List<Tile>();

            for (int i = 0; i < Width; i++)
            {
                for (int j = 0; j < Height; j++)
                {
                    pathValue[i, j] = int.MaxValue;
                    tmp.Add(tiles[i, j]);
                }
            }
            pathValue[x1, y1] = 1;

            //shuffle
            int n = tmp.Count;
            while (n > 1)
            {
                n--;
                int k = random.Next(n + 1);
                Tile t = tmp[k];
                tmp[k] = tmp[n];
                tmp[n] = t;
            }

            tilePool.UnionWith(tmp);
            tilePool.RemoveWhere(isColorSwitch);


            while (tilePool.Count != 0)
            {
                Tile tile = null;
                int minValue = int.MaxValue;
                foreach (Tile t in tilePool)
                {
                    if (pathValue[t.X, t.Y] < minValue)
                    {
                        tile = t;
                        minValue = pathValue[t.X, t.Y];
                    }
                }
                markedTiles.Add(tile);
                tilePool.Remove(tile);

                foreach (Tile nextTile in getAdjacentTiles(tile))
                {
                    if (!markedTiles.Contains(nextTile))
                    {
                        if (minValue + getTileValue(nextTile, colorID) < pathValue[nextTile.X, nextTile.Y])
                        {
                            pathValue[nextTile.X, nextTile.Y] = minValue + getTileValue(nextTile, colorID);
                            previousTile[nextTile.X, nextTile.Y] = tile;
                        }
                    }
                }

                if (isBorder(tile))
                {
                    foreach (Tile nextTile in getBorderTiles())
                    {
                        if (!markedTiles.Contains(nextTile))
                        {
                            if (minValue + getTileValue(nextTile, colorID) * borderFactor < pathValue[nextTile.X, nextTile.Y])
                            {
                                pathValue[nextTile.X, nextTile.Y] = minValue + getTileValue(nextTile, colorID) * borderFactor;
                                previousTile[nextTile.X, nextTile.Y] = tile;
                            }
                        }
                    }
                }
            }

            List<Tile> path = new List<Tile>();
            Tile currentTile = tiles[x2,y2];
            while (currentTile.X != x1 || currentTile.Y != y1)
            {
                path.Add(currentTile);
                currentTile = previousTile[currentTile.X, currentTile.Y];
            }

            foreach (Tile tile in path)
            {
                if (tile.Type == TileType.Blocked)
                {
                    tile.Type = TileType.Waypoint;
                    tile.WaypointID = colorID;
                }
                else if (tile.Type == TileType.Waypoint) tile.Type = TileType.Normal;
            }

            if (currentTile.Type == TileType.Blocked || (currentTile.Type == TileType.Waypoint && currentTile.WaypointID != colorID))//blocked => first tile of first path
            {
                currentTile.Type = TileType.ColorSwitch;
                currentTile.WaypointID = colorID;
            }

            path.RemoveAt(path.Count - 1);
            return path;
        }

    }
}
